//
// CATGL - Cheap Action-game Tool with WebGL.
//
// Copyright 2010, tmasda.
// All rights reserved.
//
// Redistribution and use in source and binary forms, with or without
// modification, are permitted provided that the following conditions are
// met:
//
//     * Redistributions of source code must retain the above copyright
// notice, this list of conditions and the following disclaimer.
//     * Redistributions in binary form must reproduce the above
// copyright notice, this list of conditions and the following disclaimer
// in the documentation and/or other materials provided with the
// distribution.
//
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

function Model(){}

Model.create = function(){
    var model = new Model;
    return model;
}


function loadBlenderModelTexture(texName, idx){
    var gl = catGL.gl;
    var tex = gl.createTexture();
    tex.image = new Image();
    // 読み込み完了時に呼ばれる関数
    tex.image.onload = function() { 
        gl.bindTexture(gl.TEXTURE_2D, tex);
        gl.pixelStorei(gl.UNPACK_FLIP_Y_WEBGL, true);// これがないとtextureが上下逆さまに張られる
        gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, gl.RGBA, gl.UNSIGNED_BYTE, tex.image);
        gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.LINEAR);
        gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.LINEAR_MIPMAP_NEAR);
        gl.generateMipmap(gl.TEXTURE_2D);
        gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE);
        gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE);
        gl.bindTexture(gl.TEXTURE_2D, null);	
    }
    // 画像データのパスを設定
    tex.image.src = catGL.lib.resourcePath + texName;
    return tex;
}

// blenderから出力されたArmatureの読み込み
// childとしてModelがあれば合わせて読み込む
function createBlenderArmature(topObject, blenderArmature, shaderFunc){
    var gl = catGL.gl;
    var arm = Model.create();
    var i;

    if (topObject.isYup){
        // Yupの時には単位行列を利用する
        arm.matrix = mat4.createI();
    } else {
        // ModelがZupの時にはトップのオブジェクトに変換のための行列を加える
        arm.matrix = mat4.createRotateX(-0.5*Math.PI);
    }

    // childが存在すれば階層構造を追加する
    if (blenderArmature.child) {
        arm.child = [];
        for (i = 0; i < blenderArmature.child.length; i++){
            var childModel = {};
            var child = blenderArmature.child[i];
            childModel = createBlenderModel(topObject, child, shaderFunc, true);
            arm.child.push(childModel);
        }
    }
    arm.isArmature = true; // 描画対象から外すためのフラグ

    return arm;
}

function createBlenderModel(topObject, blenderModel, shaderFunc, childFlag){
    var gl = catGL.gl;
    var model = Model.create();
    var i;
    if (shaderFunc){
        model.shaderName = shaderFunc(blenderModel)
    }
    var numItems = blenderModel.numItem;

    //  modelに設定するmatrix
    if (!childFlag) {
        if (topObject.isYup){
            // Yupの時にはそのまま行列を利用する
            model.matrix = blenderModel.matrix;
        } else {
            // ModelがZupの時にはトップのオブジェクトに変換のための行列を加える
            model.matrix = mat4.createRotateX(-0.5*Math.PI);
            if (blenderModel.matrix){
                mat4.multiply(model.matrix, blenderModel.matrix);
            }
        }
    } else {
        // 一番上のオブジェクトでなければmatrixは持っているものをそのまま使う
        if (blenderModel.matrix){
            model.matrix = mat4.create(blenderModel.matrix);
        }
    }

    // vertex buffer
    model.vertexBuffer = gl.createBuffer();
    gl.bindBuffer(gl.ARRAY_BUFFER, model.vertexBuffer);
    gl.bufferData(gl.ARRAY_BUFFER, new Float32Array(blenderModel.vertices), gl.STATIC_DRAW);

    // texture座標
    if (blenderModel.texCoords){
        model.texCoordBuffer = gl.createBuffer();
        gl.bindBuffer(gl.ARRAY_BUFFER, model.texCoordBuffer);
        gl.bufferData(gl.ARRAY_BUFFER, new Float32Array(blenderModel.texCoords), gl.STATIC_DRAW);
    }

    // 法線
    if (blenderModel.normals){
        model.normBuffer = gl.createBuffer();
        gl.bindBuffer(gl.ARRAY_BUFFER, model.normBuffer);
        gl.bufferData(gl.ARRAY_BUFFER, new Float32Array(blenderModel.normals), gl.STATIC_DRAW);
    }

    // material diffuse
    if (blenderModel.diffuses){
        model.diffuses = blenderModel.diffuses;
    }
    // material speculars
    if (blenderModel.speculars){
        model.speculars = blenderModel.speculars;
    }
    // vertex color
    if (blenderModel.vertColors){
        model.colorBuffer = gl.createBuffer();
        gl.bindBuffer(gl.ARRAY_BUFFER, model.colorBuffer);
        gl.bufferData(gl.ARRAY_BUFFER, new Float32Array(blenderModel.vertColors), gl.STATIC_DRAW);
    }
    
    // textureの読み込みと設定
    if (!topObject.tex) { // texが生成されていないときだけ
        topObject.tex = [];
        for (i = 0; i < topObject.texList.length; i++){
            topObject.tex[topObject.texList[i]] = loadBlenderModelTexture(topObject.texList[i]); 
        }
    }
    if (blenderModel.texName){
        model.tex = topObject.tex[blenderModel.texName];
    }
    
    if (blenderModel.indices){
        // index用のbufferの作成
        model.indexBuffer = gl.createBuffer();
        gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, model.indexBuffer);
        gl.bufferData(gl.ELEMENT_ARRAY_BUFFER, new Uint16Array(blenderModel.indices), gl.STATIC_DRAW);
        // indexを利用する場合の表示関数
        model.drawFunc = function(){
            gl.drawElements(gl.TRIANGLES, numItems, gl.UNSIGNED_SHORT, 0);
        };
    } else {
        // indexを利用しない場合の表示関数
        model.drawFunc = function(){
            gl.drawArrays(gl.TRIANGLES, 0, numItems);
        };
    }

    // childが存在すれば再帰的に階層構造を追加する
    if (blenderModel.child) {
        model.child = [];
        for (i = 0; i < blenderModel.child.length; i++){
            var childModel = {};
            var child = blenderModel.child[i];
            childModel = createBlenderModel(topObject, child, shaderFunc, true);
            model.child.push(childModel);
        }
    }

    return model;
}

function traceModelHierarchy(model, fn){
    var i;
    if (!model.isArmature){
        fn(model);
    }
    if (model.child){
        for (i = 0; i < model.child.length; i++){
            traceModelHierarchy(model.child[i], fn);
        }
    }
}

// modelのshaderNameをセットする（階層に対応）
Model.prototype.setShaderName = function(shaderName){
    traceModelHierarchy(this, function(model){
            model.shaderName = shaderName;
            });
}
// modelのshaderNameをセットする（階層に対応）
Model.prototype.setShader = function(func){
    traceModelHierarchy(this, function(model){
                func(model);
            });
}

// 頂点と法線（あれば）をmatrixで変換する
function convertModelVertex(blenderModel, matrix) {
    var numVertex = blenderModel.indices.length;
    var vertex = blenderModel.vertices;
    var norm = blenderModel.normals;
    var normMatrix;

    mat4.multiplyVec3Array(matrix, vertex);
    // 法線が有る場合には法線も変換
    // todo:テスト
    if (norm){
        normMatrix = mat4.create();
        mat4.toInverseMat3(matrix, normMatrix);
        mat3.transpose(normMatrix);
        mat3.multiplyVec3Array(normMatrix, norm);
    }
}

// XZ平面上の円モデル
function createCircleModel(center, size, div, color0, color1) {
    var gl = catGL.gl;
    var model = Model.create();
    model.shaderName = "vertex-color";
    var angle = 2*Math.PI / div;
    var i;
    var vertices = new Float32Array(3*(div+2));
    vertices[0] = center[0];
    vertices[1] = center[1];
    vertices[2] = center[2];
    for (i = 0; i < div+1; i++){
        vertices[3+3*i+0] = Math.cos(angle*i)*size;
        vertices[3+3*i+1] = center[2];
        vertices[3+3*i+2] = Math.sin(angle*i)*size;
    }
    
    model.vertexBuffer = gl.createBuffer();
    gl.bindBuffer(gl.ARRAY_BUFFER, model.vertexBuffer);
    gl.bufferData(gl.ARRAY_BUFFER, vertices, gl.STATIC_DRAW);

    var colors = new Float32Array(4*(div+2));
    colors[0] = color0[0];
    colors[1] = color0[1];
    colors[2] = color0[2];
    colors[3] = color0[3];
    for (i = 0; i < div+1; i++){
        colors[4+i*4+0] = color1[0];
        colors[4+i*4+1] = color1[1];
        colors[4+i*4+2] = color1[2];
        colors[4+i*4+3] = color1[3];
    }
    model.colorBuffer = gl.createBuffer();
    gl.bindBuffer(gl.ARRAY_BUFFER, model.colorBuffer);
    gl.bufferData(gl.ARRAY_BUFFER, colors, gl.STATIC_DRAW);

    model.drawFunc = function(){
        gl.drawArrays(gl.TRIANGLE_FAN, 0, div+2);
    };
    return model;
    
}


function createWhiteTriangleModel(scale) {
    var gl = catGL.gl;
    var model = Model.create();
    model.shaderName = "white";

    model.vertexBuffer = gl.createBuffer();
    gl.bindBuffer(gl.ARRAY_BUFFER, model.vertexBuffer);

    var s = scale || 1.0;
    var vertices = new Float32Array([
        0.0,  s,  0.0,
         -s, -s,  0.0,
          s, -s,  0.0
    ]);
    gl.bufferData(gl.ARRAY_BUFFER, vertices, gl.STATIC_DRAW);
    model.drawFunc = function(){
        gl.drawArrays(gl.TRIANGLES, 0, 3);
    };

    return model;
}

function createWhiteSquareModel(scale) {
    var gl = catGL.gl;
    var model = Model.create();
    model.shaderName = "white";

    model.vertexBuffer = gl.createBuffer();
    gl.bindBuffer(gl.ARRAY_BUFFER, model.vertexBuffer);

    var s = scale || 1.0;
    var vertices = new Float32Array([
         s,  s,  0.0,
        -s,  s,  0.0,
         s, -s,  0.0,
        -s, -s,  0.0
    ]);
    gl.bufferData(gl.ARRAY_BUFFER, vertices, gl.STATIC_DRAW);
    model.drawFunc = function(){
        gl.drawArrays(gl.TRIANGLE_STRIP, 0, 4);
    };
    return model;
}

// XYZをRBGで表す3軸のモデル
function createAxisModel(scale) {
    var gl = catGL.gl;
    var model = Model.create();
    model.shaderName = "vertex-color";

    var s = scale || 1.0;
    var min = -0.2 * s;
    var vertices = new Float32Array([
        min, 0, 0,
        s, 0, 0,
        0, min, 0,
        0, s, 0,
        0, 0, min,
        0, 0, s
    ]);
    model.vertexBuffer = gl.createBuffer();
    gl.bindBuffer(gl.ARRAY_BUFFER, model.vertexBuffer);
    gl.bufferData(gl.ARRAY_BUFFER, vertices, gl.STATIC_DRAW);

    var colors = new Float32Array([
        1, 0, 0, 1,
        1, 0, 0, 1,
        0, 1, 0, 1,
        0, 1, 0, 1,
        0, 0, 1, 1,
        0, 0, 1, 1,
    ]);
    model.colorBuffer = gl.createBuffer();
    gl.bindBuffer(gl.ARRAY_BUFFER, model.colorBuffer);
    gl.bufferData(gl.ARRAY_BUFFER, colors, gl.STATIC_DRAW);

    model.drawFunc = function(){
        gl.drawArrays(gl.LINES, 0, 6);
    };
    return model;
}
